(function($) {
	var formatNumber = function(e, decimal, style, currencySymbol, prefix, suffix) {
		var positive = true;
		if (e < 0) {
			positive = false;
			e = e * -1;
		}

		if (suffix == '%')
			e = e * 100;

		if ($.type(decimal) == 'string') {
			var pow = Math.pow(10, parseInt(decimal))
			e = (Math.round(pow * e) / pow).toString();
		} else {
			e = e.toFixed(decimal);
		}
		var s = e.replace(/^(\d+)((\.\d*)?)$/, function(v1, v2, v3) {
        	if (style || $E.currencyStyle)
        		return (currencySymbol ? v2.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&.') : v2) + (v3 ? (',' + v3.substring(1)):'');
        	else
        		return (currencySymbol ? v2.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,') : v2) + (v3||'');
        })

        s = (positive ? '' : '-') + s;
        if (prefix)
        	s = prefix + s;
        if (suffix)
        	s = s + suffix;
        return s;
	}

	var CtlUtils = {
		parseIcon : function(iconObj) {
			if (! iconObj || $.type(iconObj) == 'string')
				return iconObj || '';

			if (iconObj.t == 2)
				return $('<img src="' + $E.url + '/images/' + iconObj.n + '" />');

			if (iconObj.t == 3) {
				return $('<svg class="icon svg-icon" aria-hidden="true" style="font-size:' + iconObj.s + 'em; fill:' + iconObj.c + '"><use xlink:href="#icon-' + iconObj.n +'"></use></svg>')
			}

			// 自定义图标
			var c,
				icon,
				appending = '',
				k;
			if (iconObj.n[0] == '@') {
				appending += (iconObj.t == 1 ? ' nf-spin' : ' fa-spin');
				icon = iconObj.n.substring(1);
			} else
				icon = iconObj.n;

			k = icon.indexOf('#');
			if (k !== -1) {
				iconObj.c = icon.substring(k);
				icon = icon.substring(0, k);
			}

			if (iconObj.d == 2 || iconObj.d == 3) {
				appending += ' vertical';
			}
			if (icon.startWith('{') && icon.endWith('}')) {
				c = icon.substring(1, icon.length - 1);
			} else {
				if (iconObj.t == 1)
					c = 'nf nf-fw nf-' + icon + appending;
				else
					c = 'fa-fw ' + $F.fas(icon) + appending;
			}

			var style = [];
			if (iconObj.s)
				style.push('font-size:' + iconObj.s + 'em');
			if (iconObj.c)
				style.push('color:' + iconObj.c);

			return $('<i class="' + c + '" style="' + style.join(';') + '"></i>');
		},

		expression : function(e, formula, runtime) {
			if (!formula)
				return e;

			var nf = (/^N\((.*)\)/gi).exec(formula),
				sf = (/^S\((.*)\)/gi).exec(formula),
				df = (/^D\((.*)\)/gi).exec(formula),
				cu = (/^C\((.*)\)/gi).exec(formula),	// C(小数点位, 是否法式表达, 是否货币格式, 货币符号)
				cu2 = (/^C2\((.*)\)/gi).exec(formula),	// C(小数点位, 是否法式表达, 是否货币格式, 货币符号)
				method = (/^METHOD\((.*)\)/gi).exec(formula),
				format = (/^FORMAT\((.*)\)/gi).exec(formula),
				mask = (/^MASK\((.*)\)/gi).exec(formula);

			if (nf) {
				// 数字格式化
				return $F.formatNumber(e, nf[1]);
			} else if (cu) {
				e = parseFloat(e);
				if (isNaN(e))
					return '';
				var args = eval('[' + cu[1] + ']');
				return formatNumber.apply(null, Array.prototype.concat(e, args))
			} else if (cu2) {
				e = parseFloat(e);
				if (isNaN(e))
					e = 0;
				var args = eval('[' + cu2[1] + ']');
				return formatNumber.apply(null, Array.prototype.concat(e, args))
			} else if (sf) {
				if ($.type(e) !== 'string')
					return $F.err('Ctls - Table', '非字符串不可进行字符串格式化'), '';
				// 字符串格式化
				var xss = Helper.filter(e);
				return $F.formatString(xss, sf[1]);
			} else if (df) {
				// 日期格式化
				if (!e)
					return ''

				if ($.type(e) == 'number') {
					var date = new Date(e);
					return date.format(df[1]);
				} else if ($.type(e) == 'string') {
					var format,
						st = e.split(' ');
					if (st.length < 2) {
						var subt = st[0].split(':');
						if (subt.length > 1) {
							// 字符串为时间
							if (subt.length == 2)
								format = "H:i";
							else
								format = "H:i:s";
						} else {
							// 字符串为日期
							format = "Y-m-d";
						}
					} else {
						// 字符串为完整日期时间
						var subt = e.trim().split(':');
						if (subt.length > 1) {
							// 字符串为时间
							if (subt.length == 2)
								format = "Y-m-d H:i";
							else
								format = "Y-m-d H:i:s";
						} else {
							// 字符串为日期
							format = "Y-m-d H";
						}
					}

					var date = Date.parseDate(e, format);
					//console.log(e + ' parse to "' + format + '"', date)
					if (!date) {
						$F.err('转换日期对象失败：' + e);
						return '';
					}
					//console.log(date);
					if (date.getFullYear() == 1900)
						return '';

					return date.format(df[1]);
				} else {
					$F.err('Ctls - Table', '不合法的日期格式化对象:' + $.type(e));
					return ''
				}
			} else if (method) {
				return runtime.instance.methods && runtime.instance.methods[method[1]] && runtime.instance.methods[method[1]].call(runtime.instance, e);
			} else if (format) {
				let x = format[1].split(','),
					start = parseInt(x[0]),
					end = 0;
				if (x.length > 1)
					end = parseInt(x[1])
				return $F.Masker.formatMask(e, start, end);
			} else if (mask) {
				if (!mask[1]) {
					var proj = (runtime && runtime.project) ? runtime.project : $E.getActiveProject();
					return proj.shieldString(e);
				} else {
					return $F.shieldMask(e, mask[1]);
				}
			} else {
				if (e == undefined)
					return '';
				if (formula.toLowerCase() == 'totime') {
					var seconds = parseInt(e); // 秒
					var minites = 0; // 分
					var hours = 0; // 小时

					minites = parseInt(seconds / 60);
					seconds = parseInt(seconds % 60); // 余秒
					hours = parseInt(minites / 60);
					minites = parseInt(minites % 60); // 余分

					var hoursStr = hours > 9 ? hours : ('0' + hours);
					var minitesStr = minites > 9 ? minites : ('0' + minites);
					var seconsStr = seconds > 9 ? seconds : ('0' + seconds);
					return hoursStr + ':' + minitesStr + ':' + seconsStr;
				} else if (formula.toLowerCase() === 'mask') {
					var proj = (runtime && runtime.project) ? runtime.project : $E.getActiveProject();
					return proj.shieldString(e);
				} else {
					$F.err('CtlUtils - expression', '不能识别的公式： ' + formula);
					return e;
				}
			}
		},

		/**
		 * 删除列表项中的某一行数据（此方法仅仅是去除UI列，并不会改变数据）
		 * @method xitemRemove
		 * @param runtime {object} 运行环境
		 * @param e {string|object} XITEM列表项控件的ID，或者XITEM控件对象
		 * @param row {int} 行号，下标为0
		 */
		xitemRemove : function(runtime, e, row) {
			var ctl;
			if ($.type(e) == 'string') {
				// 入参是控件ID
				ctl = runtime.ctls[e];
				if (!ctl)
					return $F.err('CtlUtils - xitemRemove', '不存在的列表项控件, ID：' + e), false;
			} else {
				ctl = e;
			}

			if (ctl.type != 'XITEM') {
				return $F.err('CtlUtils - xitemRemove', '指定的控件不是列表项, ID：' + e), false;
			}

			ctl.remove(row);
			return true;
		},

		/**
		 * 让panel在指定的控件上做漂移效果，命令中使用R.CTRL代入ctl对象
		 * @method driftLayer
		 * @param runtime {object} 运行环境
		 * @param e {string|object} 触发事件的控件对象
		 * @param layerId {string} 漂移的PANEL控件ID
		 */
		driftLayer : function(runtime, e, layerId) {
			var ctl;
			if ($.type(e) == 'string') {
				// 入参是控件ID
				ctl = runtime.ctls[e];
				if (!ctl)
					return $F.err('CtlUtils - driftLayer', '不存在的目标控件, ID：' + e);
			} else {
				ctl = e;
			}

			if (!layerId)
				return $F.err('CtlUtils - driftLayer', '无效的浮动层ID');

			var layer = runtime.ctls[layerId];
			if (!layer)
				return $F.err('CtlUtils - driftLayer', '没有找到浮动层：' + layerId);
			if ((runtime.struct.version == 'DYN2' && layer.type !== 'LAYER') || (runtime.struct.version !== 'DYN2' && layer.type !== 'PANEL'))
				return $F.err('CtlUtils - driftLayer', '不是合法的浮动层：' + layerId);

			if ($.isFunction(ctl.driftLayer)) {
				setTimeout(function() {
					ctl.driftLayer(layer, {});
				}, 1)
			} else
				$F.err('CtlUtils - driftLayer', '控件不支持浮动层操作');
		},

		/**
		 * 切换目标控件的风格
		 * @method toggleClass
		 * @param runtime {object} 运行环境
		 * @param e {string|object} 切换风格的控件对象
		 * @param className {string} 风格的CSS名称
		 * @param expression {string} 条件
		 */
		toggleClass : function(runtime, e, className, expression) {
			var ctl;
			if ($.type(e) == 'string') {
				// 入参是控件ID
				ctl = runtime.ctls[e];
				if (!ctl)
					$F.err('CtlUtils - toggleClass', '不存在的目标控件, ID：' + e);
			} else {
				ctl = e;
			}
			if ($.isFunction(ctl.toggleClass))
				ctl.toggleClass(className, expression);
			else
				$F.err('CtlUtils - toggleClass', '控件不支持风格变更操作');
		},

		/**
		 * 自行某个控件的内容过滤方法
		 * @method filter
		 * @param runtime {object} 运行环境
		 * @param e {string|object} 提供过滤内容的控件
		 * @param filterCtlId {string} 执行过滤的控件ID
		 */
		filter : function(runtime, e, filterCtlId) {
			var ctl;
			if ($.type(e) == 'string') {
				// 入参是控件ID
				ctl = runtime.ctls[e];
				if (!ctl)
					return $F.err('CtlUtils - filter', '不存在的目标控件, ID：' + e);
			} else {
				ctl = e;
			}

			var filterCtl = runtime.ctls[filterCtlId];
			if (!filterCtl)
				$F.err('CtlUtils - filter', '不存在的过滤控件, ID：' + filterCtlId);

			if ($.isFunction(filterCtl.filter))
				filterCtl.filter(ctl.val());
			else
				$F.err('CtlUtils - filter', '控件不支持过滤操作:' + target.type);

		},

		/**
		 * 执行某个控件的内容搜索方法
		 * @method search
		 * @param runtime {object} 运行环境
		 * @param ctl {string | object} 提供过滤内容的控件
		 * @param searchCtl {string} 执行过滤的控件ID
		 */
		search : function(runtime, ctl, searchCtl) {
			if ($.type(ctl) == 'string') {
				if (!runtime.ctls[ctl])
					return $F.err('CtlUtils - search', '不存在的目标控件, ID：' + ctl);
				ctl = runtime.ctls[ctl];
			}

			if ($.type(searchCtl) == 'string') {
				if (!runtime.ctls[searchCtl])
					return $F.err('CtlUtils - search', '不存在的过滤控件, ID：' + searchCtl);
				searchCtl = runtime.ctls[searchCtl];
			}

			if ($.isFunction(searchCtl.search))
				searchCtl.search(ctl.val());
			else
				return $F.err('CtlUtils - search', '控件不支持查询操作:' + searchCtl.type);

		},

		/**
		 * 调用控件的值改变命令
		 * @method fireChanged
		 * @param runtime {object} 运行环境
		 * @param ctl {string | object} 控件ID或对象
		 * @param e {string} 事件名称
		 */
		fireCtl : function(runtime, ctl, e) {
			if ($.type(ctl) == 'string') {
				if (!runtime.ctls[ctl])
					return $F.err('CtlUtils - search', '不存在的目标控件, ID：' + ctl);
				ctl = runtime.ctls[ctl];
			}

			if ($.isFunction(ctl.trigger))
				ctl.trigger(e);
		},

		/**
		 * 调用组件的某个命令
		 * @method fireComponentCmd
		 * @param instance {instance} 实例对象
		 * @param cmdId {string} 命令号
		 * @param [path] {string} 组件在页面中的path，为空时代表执行该实例下所有组件的cmdId命令
		 * @param [ev] {object} 命令环境
		 * @param [silent] {boolean} 是否使用安静模式，在安静模式中，组件对象不存在时不再进行错误报警
		 */
		fireComponentCmd : function(runtime, cmdId, path, ev, quitely) {
			// alert(cmdId);
			if (path && runtime.namespace)
				path = runtime.namespace + '.' + path;
			if ($E.debug)
				console.log('fireComponentCmd: ' + cmdId + (path ? ('@' + path) : ''));
			//			if (runtime.status !== 'ready')
			//				return;

			if (path) {
				// 指定组件的namespace
				var component = this.findComponentByNS(runtime, path);
				if (component)
					component.doCmd(component.struct.version == 'DYN2' ? cmdId : ('P' + cmdId), $.extend({}, ev));
				else {
					if (! quitely)
						$F.err('CtlUtils - fireComponentCmd', '组件[' + path + ']不存在', runtime);
				}
			} else {
				// 未指定组件的namespace，遍历当前runtime下的所有组件
				_fireComp(runtime, path, cmdId);
			}
		},

		findComponentByNS : function(runtime, namespace) {
			//console.log(namespace + ' vs ' + runtime.namespace)
			if (namespace == runtime.namespace)
				return runtime.component;

			var comp;
			for (var key in runtime.children) {
				if (runtime.children.hasOwnProperty(key)) {
					comp = this.findComponentByNS(runtime.children[key], namespace);
					if (comp)
						return comp;
				}
			}
		},

		findComponentById : function(runtime, id) {
			if (id == runtime.id)
				return runtime.component;

			var comp;
			for (var key in runtime.children) {
				if (runtime.children.hasOwnProperty(key)) {
					comp = this.findComponentById(runtime.children[key]);
					if (comp)
						return comp;
				}
			}
		},

		findAllComponents : function(runtime) {
			var cs = {},
				find = function(v, coms) {
					if (v.component)
						coms[v.namespace] = v.component;
					$.each(v.children, function(i, sub) {
						find(sub, coms)
					})
				};
			find(runtime, cs);
			return cs;
		},

		/**
		 * 调用控件的setScript方法
		 * @method setScript
		 * @param runtime {object} 运行环境
		 * @param e {string|object} 控件对象
		 * @param script {string} script脚本
		 */
		setScript : function(runtime, e, script) {
			var ctl;
			if ($.type(e) == 'string') {
				// 入参是控件ID
				ctl = runtime.ctls[e];
				if (!ctl)
					return $F.err('CtlUtils - search', '不存在的目标控件, ID：' + e);
			} else {
				ctl = e;
			}
			if ($.isFunction(ctl.setScript))
				return ctl.setScript(script);

			return $F.err('CtlUtils - setScript', '控件不支持setScript方法：' + ctl.type);
		},

		setWoInfo : function(ctl) {
			ctl.runtime.instance.woInfoLayer = ctl;
		},

		/**
		 * 为某个控件设置一个计时器，计时结果直接赋值给控件。重复设置时相当于复位
		 * @method makeTimer
		 * @param sandbox {object} 运行沙箱，函数使用时直接入参this
		 * @param ctl {object | string} 控件对象或者ID
		 * @param [initSeconds] {integer} 初始秒数，默认为0
		 */
		makeTimer : function(sandbox, ctl, initSeconds) {
			if ($.type(ctl) == 'string') {
				// 入参是控件ID
				var ctl2 = sandbox.$runtime.ctls[ctl];
				if (!ctl2) {
					$F.err('CtlUtils - makeTimer', '不存在的目标控件, ID：' + ctl);
					return;
				}
				ctl = ctl2;
			}

			// putTask : function(runtime, name, delay, immediate, cmdId, ev)
			var name,
				startTime = new Date() - (initSeconds || 0) * 1000;
			if (ctl.timerName) {
				name = ctl.timerName;
				sandbox.$instance.clearTask(ctl.timerName);
			} else
				name = Guid.raw();

			sandbox.$instance.putTask(sandbox.$runtime, name, 1, 0, function() {
				ctl.val(long2time(new Date() - startTime) || 'N/A');
			})
			ctl.timerName = name;
		},


		/**
		 * 使某个控件获得焦点
		 * @param ctlId {string} 控件ID
		 * @param runtime {object} 运行环境
		 */
		ctlFocus : function(ctlId, runtime) {
			var ctl = runtime.ctls[ctlId];
			if (!ctl)
				$F.err('CtlUtils - focus', '控件不存在，ID:' + ctlId);
			if (!$.isFunction(ctl.focus))
				$F.err('CtlUtils - focus', '控件不支持FOCUS事件，TYPE:' + ctl.type);
			ctl.focus();
		},

		/**
		 * 使某个控件的上一个控件获得焦点
		 * @param ctlId {string} 控件ID
		 * @param runtime {object} 运行环境
		 */
		ctlPrevFocus : function(ctlId, runtime) {
			var ctl = runtime.ctls[ctlId];
			if (!ctl)
				$F.err('CtlUtils - focus', '控件不存在，ID:' + ctlId);
			if (ctl.prevCtl) {
				if ($.isFunction(ctl.prevCtl.focus))
					ctl.prevCtl.focus();
				else
					this.ctlPrevFocus(ctl.prevCtl.id)
			}
		},


		/**
		 * 使某个控件的下一个控件获得焦点
		 * @param ctlId {string} 控件ID
		 * @param runtime {object} 运行环境
		 */
		ctlNextFocus : function(ctlId, runtime) {
			var ctl = runtime.ctls[ctlId];
			if (!ctl)
				$F.err('CtlUtils - focus', '控件不存在，ID:' + ctlId);
			if (ctl.nextCtl) {
				if ($.isFunction(ctl.nextCtl.focus))
					ctl.nextCtl.focus();
				else
					this.ctlNextFocus(ctl.nextCtl.id)
			}
			return;
		},

		/**
		 * 从控件的配置脚本中获取指定的值
		 * @param ctlProfile {object} 控件的配置脚本，在命令中通过R.CTRL.profile获得
		 * @param path {string} 配置脚本中的属性路径，ve中的控件属性包括四个大类：styles, general, local, extend
		 * @return {any} 控件配置的属性值
		 * @example:
		 * 		var value = CtlUtils.getProfile(R.CTRL.profile, 'styles.state');
		 * 		out(value)
		 *
		 */
		getProfile : function(ctlProfile, path) {
			if (ctlProfile && path) {
				if (ctlProfile.props) {
					var cs = path.split('.');
					if (ctlProfile.props[cs[0]]) {
						return ctlProfile.props[cs[0]][cs[1]];
					}
				}
			}
		},

		/**
		 * 设置控件的配置脚本中指定的属性值
		 * @param ctlProfile {object} 控件的配置脚本，在命令中通过R.CTRL.profile获得
		 * @param path {string} 配置脚本中的属性路径，ve中的控件属性包括四个大类：styles, general, local, extend
		 * @param value {any} 控件配置的属性值
		 * @example:
		 * 		CtlUtils.setProfile(R.CTRL.profile, 'styles.state', 0);
		 *
		 */
		setProfile : function(ctlProfile, path, value) {
			if (ctlProfile && path) {
				ctlProfile.props = ctlProfile.props || {}
				var cs = path.split('.');
				ctlProfile.props[cs[0]] = ctlProfile.props[cs[0]] || {};
				ctlProfile.props[cs[0]][cs[1]] = value;
			}
		},

		/**
		 * 从控件的配置自定义属性中获取指定的值
		 * @param ctlProfile {object} 控件的配置脚本，在命令中通过R.CTRL.profile获得
		 * @param key {string} 配置脚本中的自定义属性
		 * @example:
		 * 		var value = getAttribute(R.CTRL.profile, 'key');
		 * 		out(value)
		 *
		 */
		getAttr : function(ctlProfile, key) {
			if (ctlProfile && key) {
				if (ctlProfile.props) {
					if (ctlProfile.props['local']) {
						if (ctlProfile.props['local'].attributes) {
							return ctlProfile.props['local'].attributes[key];
						}
					}
				}
			}
		},

		/**
		 * 设置控件的配置脚本中的自定义属性
		 * @param ctlProfile {object} 控件的配置脚本，在命令中通过R.CTRL.profile获得
		 * @param key {string} 配置脚本中的自定义属性
		 * @param value {any} 自定义属性的值
		 * @example:
		 * 		setAttribute(R.CTRL.profile, 'key', 'myValue');
		 *
		 */
		setAttr : function(ctlProfile, key, value) {
			if (ctlProfile && key) {
				ctlProfile.props = ctlProfile.props || {};
				ctlProfile.props['local'] = ctlProfile.props['local'] || {};
				ctlProfile.props['local'].attributes = ctlProfile.props['local'].attributes || {};
				ctlProfile.props['local'].attributes[key] = value;
			}
		}
	};

	var long2time = function(value) {
			try {
				value = value / 1000;
				var seconds = parseInt(value); // 秒
				var minites = 0; // 分
				var hours = 0; // 小时

				minites = parseInt(seconds / 60);
				seconds = parseInt(seconds % 60); // 余秒
				hours = parseInt(minites / 60);
				minites = parseInt(minites % 60); // 余分
				var hoursStr = hours > 9 ? hours : ('0' + hours);
				var minitesStr = minites > 9 ? minites : ('0' + minites);
				var seconsStr = seconds > 9 ? seconds : ('0' + seconds);
				return hoursStr + ':' + minitesStr + ':' + seconsStr;
			} catch (e) {
				console.error(e);
			}
		},

		_fireComp = function(runtime, path, cmdId) {
			$.each(runtime.children, function(i, sub) {
				if (sub.component && sub.namespace == path)
					sub.component.doCmd(sub.struct.version == 'DYN2' ? cmdId : ('P' + cmdId), $.extend({}, ev));
				_fireComp(sub, path, cmdId)
			})
		};

	window.CtlUtils = CtlUtils;

})(jQuery)
